<?php


namespace app\astrologer\controller;


use app\common\cachemodel\AppConfig;
use app\common\cachemodel\ZlcrAstr;
use app\common\cachemodel\ZlcrAstrMiniInfo;
use app\common\lib\Aes;
use app\common\lib\Alidayu;
use app\common\lib\exception\ApiException;
use app\common\lib\IAuth;
use app\common\lib\mini\alipay\AmpHelper;
use app\common\lib\Util;
use GuzzleHttp\Exception\GuzzleException;
use think\Cache;
use think\Controller;
use think\Exception;
use think\Log;
use think\response\Json;

class Login extends Controller
{

    /**
     * 跳转到占星师平台，手机端或者电脑端
     */
    public function jump_wap_or_pc()
    {
        if (Util::isMobile())
        {
            $this->redirect(config('simulation_astrologer')['home_page_wap']);
        } else
        {
            $this->redirect(config('simulation_astrologer')['home_page_pc']);
        }
    }

    /**
     * @throws \Exception
     */
    public function getCode()
    {
        $tel = trim(input('tel/s', ''));

        // 手机号是否存在
        if (!preg_match("/^1[3456789]{1}\d{9}$/", $tel))
        {
            Log::write('手机号错误：' . $tel, 'error', true);
            throw new ApiException('手机号错误', 200, 0);
        }

        $appType = $this->request->header('app-type');
        if (strpos($appType, 'manage') === false && !in_array($appType, ['WEAPP', 'PC', 'WEB', 'android', 'ios', 'flutter-web']))
        {
//            Log::write('咨询师获取验证码接口，被盗刷，ip=' . $this->request->ip() . ',app-type：' . $appType . ',tel=' . $tel, 'error', true);
            //假的发送成功
            return show(config('code.success'), '发送成功');
        }

        $code = random_int(100000, 999999);// 六位验证码

        $alidayu = (new AppConfig())->configInfo('alidayu');

        $result = Alidayu::getInstance()->sendSms($tel, $code, $alidayu['login_code']);

        if (!$result)
        {
            return show(config('code.error'), '发送失败');
        }
        $key_of_tel_code_login_error_num = 'login_error_zhunle_astrologer:' . $tel;
        Cache::rm($key_of_tel_code_login_error_num); // 登录
        $key_of_tel_code_bind_error_num = 'bind_tel_error_zhunle_astrologer:' . $tel;
        Cache::rm($key_of_tel_code_bind_error_num); // 绑定手机号

        return show(config('code.success'), '发送成功');
    }

    /**
     * 登陆
     */
    public function login()
    {
        $tel = trim(input('username/s', ''));
        $password = trim(input('password/s', ''));

        // 手机号是否存在
        if (!preg_match("/^1[3-9]\d{9}$/", $tel))
        {
            Log::write('手机号错误：' . $tel, 'error', true);
            throw new ApiException('手机号错误', 200, 0);
        }

        if (!$password)
        {
            return show(config('code.error'), '请输入密码');
        }

        if (!preg_match("/^(?![^a-zA-Z]+$)(?!\\\\D+$).{8,16}$/", $password))
        {
            return show(config('code.error'), '密码格式错误（8~16个字符，包含字母和数字）');
        }

        //防止暴力破解：用户名密码输入错误超过3次，那么锁定5分钟
        $key_of_user_login_error = 'login_error_zhunle_astrologer:' . $tel . ':' . $this->request->ip();
        if (Cache::has($key_of_user_login_error))
        {//存在数据
            if (Cache::get($key_of_user_login_error) >= 3)
            {
                Cache::inc($key_of_user_login_error);
                Log::write($tel . '占星师后台手机号登录失败：您输错密码超过3次，请5分钟后重试！错误次数为：' . Cache::get($key_of_user_login_error), 'error');
                return show(config('code.error'), '您输错密码超过3次，请5分钟后重试！');
            }
        }

        $astrModel = (new ZlcrAstr());
        $astrInfo = $astrModel->where(['tel' => $tel])->field('id,tel,password,salt,check_status,status,create_time,password_reset_at,token,token_expire')->find();

        $is_simulated = false;
        $simulation_astrologer = config('simulation_astrologer');
        if ($simulation_astrologer && $simulation_astrologer['is_open'] === 1)
        {//打开模拟的状态
            if ((int)$astrInfo['id'] === (int)$simulation_astrologer['from'])
            {//如果当前访问的占星师id 和配置中一样的话，那么开始模拟
                if ($simulation_astrologer['to'])
                {
                    $id = $simulation_astrologer['to'];
                    $astrInfo = $astrModel->getInfoById($id);
                    $is_simulated = true;
                } else
                {
                    Log::write('占星师后台Login模拟失败，to为空', 'error', true);
                }
            }
        }

        if ($astrInfo)
        {
            //状态 1=>正常 0=>禁止（禁止登陆） 2=>账号冻结（可以登录，但是不能接单
            if ((int)$astrInfo['status'] === 0)
            {
                Log::write($tel . '占星师后台手机号登录成功：该用户禁止登陆，请联系客服反馈问题～', 'error');
                return show(config('code.error'), '该用户禁止登陆，请联系客服反馈问题～');
            }
            $resPassword = md5(md5($password . $astrInfo['salt']) . $astrInfo['salt']);

            if ($is_simulated || $resPassword === $astrInfo['password'])
            {
                try
                {
                    if ($is_simulated)
                    {//是模拟的话，取出模拟的手机号的token
                        $token = $astrInfo['token'];
                    } else if ((isset($simulation_astrologer['is_support_muti']) && 1 === (int)$simulation_astrologer['is_support_muti']) && $astrInfo['token_expire'] > $this->request->time())
                    {//是支持多平台 的话，取出模拟的手机号的token
                        $token = $astrInfo['token'];
                        // 更新token到期时间
                        $astrModel->UpdateData($astrInfo['id'], ['id' => $astrInfo['id']], [
                            'token_expire' => strtotime('+' . config('simulation_astrologer.login_time_out_seconds') . ' seconds')
                        ]);
                    } else
                    {//如果不是模拟的话，那么不更新token
                        // 生成token  和 token到期时间
                        $token = IAuth::setAppLoginToken($tel);

                        $data = [
                            'token' => $token,
                            'token_expire' => strtotime('+' . config('simulation_astrologer.login_time_out_seconds') . ' seconds')
                        ];

                        $astrModel->save($data, ['id' => $astrInfo['id']]);
                    }
                }
                catch (\Exception $e)
                {
                    Log::write($tel . '占星师后台手机号登录失败：' . $e->getMessage() . '失败行数：' . $e->getLine(), 'error');
                    return show(config('code.error'), '登录失败，请稍后再试～');
                }

                //判断是否超过90天没有修改密码
                $password_reset_at = $astrInfo['password_reset_at'];
                if ($password_reset_at === 0)
                {
                    $password_reset_at = $astrInfo['create_time'];
                }
                $sep_seconds = time() - $password_reset_at;
                if ($sep_seconds > 60 * 60 * 24 * 90)
                {
                    Log::write($tel . '占星师后台手机号登录失败：登录失败，您超过90天未修改密码，请点击忘记密码，进行密码重置！', 'error');
                    return show(config('code.error'), '登录失败，您超过90天未修改密码，请点击忘记密码，进行密码重置！');
                }

                $roles = ['astrologer_not_completed'];//未完成章程学习的角色
                $path = '/dashboard/index';//跳转的路径，默认跳转首页
                $is_show_password_reset_msg = false;//在占星师前端是否强制展示修改密码的消息
                if ($astrInfo['check_status'] == 40)
                {
                    if ($sep_seconds > 60 * 60 * 24 * 80)
                    {//如果超过80天未修改密码，那么1.提示用户修改密码；2.让用户跳转到个人中心
                        $path = '/profile/index';
                        $is_show_password_reset_msg = true;
                    }
                    $roles = ['astrologer'];//完成章程学习的角色/特邀用户
                } else
                {
                    $path = '/account/index';//跳转到考试页面
                }
//                登录接口也不返回了，登陆完（密码登录、验证码登录、授权登录【微信、支付宝】、绑定手机号 等操作），先请求用户中心接口，获取pass_list，本地保存
//                $authStatus = $astrInfo['auth_status'];
//                if ($authStatus)
//                {
//                    $authStatus = json_decode($authStatus, true);
//                    $passList = [
//                        0 => $authStatus['custom_report'],
//                        1 => $authStatus['question_answer_astr'],
//                        2 => $authStatus['question_answer_dice'],
//                        3 => $authStatus['question_answer_tarot'],
//                    ];
//                } else
//                {
//                    $passList = [0, 0, 0, 0];
//                }


                $data = [
                    'token' => (new Aes())->encryptWithOpenssl($token . '||' . $astrInfo['id'], 'astradmin'),
                    'roles' => $roles,
                    'path' => $path,
                    'step' => User::$statusInfo[$astrInfo['check_status']],
//                    'pass_list' => $passList
                ];
                //状态 1=>正常 0=>禁止（禁止登陆） 2=>账号冻结（可以登录，但是不能接单
                if ($astrInfo['status'] === 2)
                {
                    Log::write($tel . '占星师后台手机号登录成功：您的账号已被冻结，请联系客服反馈问题～', 'error');
                    return show(406, '您的账号已被冻结，请联系客服反馈问题～', $data);//403: 在其他客户端登录; 405 未登录，前往登录；406：前端提示错误信息，但是返回的数据正常
                } else if ($is_show_password_reset_msg)
                {
                    Log::write($tel . '占星师后台手机号登录成功：您已经超过80天未修改密码，为了保障您账号的安全，请尽快修改密码！', 'error');
                    return show(406, '您已经超过80天未修改密码，为了保障您账号的安全，请尽快修改密码！', $data);
                } else
                {
                    return show(config('code.success'), '登录成功', $data);
                }
            } else
            {
                if (Cache::has($key_of_user_login_error))
                {//存在数据，那么+1
                    Cache::inc($key_of_user_login_error);
                } else
                {//不存在数据，那么初始化
                    Cache::set($key_of_user_login_error, 1, 300);
                }
            }
        } else
        {
            if (Cache::has($key_of_user_login_error))
            {//存在数据，那么+1
                Cache::inc($key_of_user_login_error);
            } else
            {//不存在数据，那么初始化
                Cache::set($key_of_user_login_error, 1, 300);
            }
        }

        return show(config('code.error'), '账号或密码错误！');
    }

    /**
     * 手机号登录
     */
    public function telLogin()
    {
        $tel = trim(input('tel/s', ''));
        $code = trim(input('code/s', ''));

        // 手机号是否存在
        if (!preg_match("/^1[3456789]{1}\d{9}$/", $tel))
        {
            Log::write('手机号错误：' . $tel, 'error', true);
            throw new ApiException('手机号错误', 200, 0);
        }

        // 短信验证码 或 密码是否存在
        if (empty($code))
        {
            return show(config('code.error'), '请输入验证码');
        }

        //防止暴力破解：手机号验证码登录失败10次后 除非重新获取验证码 否则验证码登录 验证码有效期（10分钟）内都自动错误
        $key_of_tel_code_login_error_num = 'login_error_zhunle_astrologer:' . $tel;
        if (Cache::get($key_of_tel_code_login_error_num, 0) >= 10)
        {
            Log::write($tel . '占星师后台手机号验证码登录失败超过10次，验证码失效', 'error');
            return show(config('code.error'), '短信验证码错误！');
        }

        $appConfig = config('app');
        // 短信登录时，检测验证码是否正确
        if (!in_array($tel, $appConfig['mobile'], false) || (strpos($appConfig['code'] * (substr($tel, 3, 6)), $code) !== 0))
        {
            //  validate 严格校验
            $mobileCode = Alidayu::getInstance()->checkSmsIdentify($tel, 86); // 获取验证码
            if ($code != $mobileCode)
            {// 如果验证码错误
                Cache::inc($key_of_tel_code_login_error_num);
                return show(config('code.error'), '短信验证码错误！');
            }
        }


        $astrModel = (new ZlcrAstr());
        $astrInfo = $astrModel->where(['tel' => $tel])->field('id,tel,password,salt,check_status,status,create_time,password_reset_at,token,token_expire')->find();


        if ($astrInfo)
        {
            //状态 1=>正常 0=>禁止（禁止登陆） 2=>账号冻结（可以登录，但是不能接单
            if ((int)$astrInfo['status'] === 0)
            {
                Log::write($tel . '占星师后台手机号登录成功：该用户禁止登陆，请联系客服反馈问题～', 'error');
                return show(config('code.error'), '该用户禁止登陆，请联系客服反馈问题～');
            }
            $simulation_astrologer = config('simulation_astrologer');
            try
            {
                if ((isset($simulation_astrologer['is_support_muti']) && 1 === (int)$simulation_astrologer['is_support_muti']) && $astrInfo['token_expire'] > $this->request->time())
                {//是支持多平台 的话 并且access_token没过期，取出模拟的手机号的token
                    $token = $astrInfo['token'];
                    // 更新token到期时间
                    $astrModel->UpdateData($astrInfo['id'], ['id' => $astrInfo['id']], [
                        'token_expire' => strtotime('+' . config('simulation_astrologer.login_time_out_seconds') . ' seconds')
                    ]);
                } else
                {
                    // 生成token  和 token到期时间
                    $token = IAuth::setAppLoginToken($tel);

                    $data = [
                        'token' => $token,
                        'token_expire' => strtotime('+' . config('simulation_astrologer.login_time_out_seconds') . ' seconds')
                    ];

                    $astrModel->save($data, ['id' => $astrInfo['id']]);
                }
            }
            catch (\Exception $e)
            {
                Log::write($tel . '占星师后台手机号验证码登录失败：' . $e->getMessage() . '失败行数：' . $e->getLine(), 'error');
                return show(config('code.error'), '登录失败，请稍后再试～');
            }

            $roles = ['astrologer_not_completed'];//未完成章程学习的角色

            if ($this->request->header('version', 1.0) >= 1.2)
            {
                $path = '/quiz/index';//跳转的路径，默认跳转问答
            } else
            {
                $path = '/dashboard/index';//跳转的路径，默认跳转首页
            }

            $is_show_password_reset_msg = false;//在占星师前端是否强制展示修改密码的消息
            if ($astrInfo['check_status'] == 40)
            {
                $roles = ['astrologer'];//完成章程学习的角色/特邀用户
            } else
            {
                $path = '/account/index';//跳转到考试页面
            }

            $data = [
                'token' => (new Aes())->encryptWithOpenssl($token . '||' . $astrInfo['id'], 'astradmin'),
                'roles' => $roles,
                'path' => $path,
                'step' => User::$statusInfo[$astrInfo['check_status']]
            ];
            //状态 1=>正常 0=>禁止（禁止登陆） 2=>账号冻结（可以登录，但是不能接单
            if ($astrInfo['status'] === 2)
            {
                Log::write($tel . '占星师后台手机号登录成功：您的账号已被冻结，请联系客服反馈问题～', 'error');
                return show(406, '您的账号已被冻结，请联系客服反馈问题～', $data);//403: 在其他客户端登录; 405 未登录，前往登录；406：前端提示错误信息，但是返回的数据正常
            } else if ($is_show_password_reset_msg)
            {
                Log::write($tel . '占星师后台手机号登录成功：您已经超过80天未修改密码，为了保障您账号的安全，请尽快修改密码！', 'error');
                return show(406, '您已经超过80天未修改密码，为了保障您账号的安全，请尽快修改密码！', $data);
            } else
            {
                return show(config('code.success'), '登录成功', $data);
            }

        }

        return show(440, '账户未注册！');
    }

    /**
     * 小程序登录
     */
    public function miniAppLogin()
    {
        $env_type = $this->request->header('app-type', 'PC'); // app-type = ALIPAY(支付宝小程序) ｜ WEAPP(微信小程序) ｜ WEB（H5） ｜ PC（PC后台）；default = PC
        $code = input('code/s', '');

        if (!in_array($env_type, ['ALIPAY', 'WEAPP'], true) || empty($code))
        {
            return show(config('code.error'), '服务异常～');
        }

        $miniAppConfig = config('mini.' . $env_type);

        if ($env_type === 'ALIPAY')
        {
            $params = [
                'mini_type' => 1,
                'code' => $code,
                'avatar' => input('avatar/s', ''),
                'nickname' => input('nickname/s'),
                'sex' => input('sex/d'),
                'city' => input('city/s'),
                'province' => input('province/s'),
                'country' => input('country_code/s'),
            ];
            $validate = validate('Calendar');
            if (!$validate->scene('alipay')->check($params))
            {
                // return show(config('code.error'), (string)$validate->getError());
            }
            //获取user_id，返回结果参考：https://docs.open.alipay.com/api_9/alipay.system.oauth.token
            $tokenData = AmpHelper::serConfig($miniAppConfig)->getAmpToken($code);
            if (!isset($tokenData['user_id']))
            {
                //记录支付宝通过code获取用户user_id失败的错误日志
                Log::write('占星师后台小程序Login Controller 支付宝登录异常：' . json_encode($tokenData), 'error', true);
                return show(config('code.error'), '获取用户支付宝id失败!');
            }

            return $this->userLogin(array_merge($params, $tokenData));
        } else
        {

            $encrypted_data = input('encrypted_data/s');
            $iv = input('iv/s');

            $params = [
                'mini_type' => 'WEAPP',
                'code' => $code,
                'iv' => $iv,
                'encrypted_data' => $encrypted_data
            ];
            $validate = validate('Calendar');
            if (!$validate->scene('others')->check($params))
            {
                return show(config('code.error'), (string)$validate->getError());
            }
            $params['mini_type'] = 2;

            $session_key = $this->getSessionKey($code, $env_type, $miniAppConfig);
            if (empty($session_key))
            {
                return show(config('code.error'), '获取session_key异常！');
            }
            $data = $this->wechatOrQQDecrypt($miniAppConfig['app_id'], $session_key, $encrypted_data, $iv);
            if ($data)
            {
                return $this->userLogin(array_merge($params, $data), $session_key);
            }
        }

        return show(config('code.success'), '服务异常～');
    }

    /**
     * 用户登录统一处理
     * @param array $data
     * @param string $sessionKey
     * @return Json|null
     * @throws ApiException
     * @throws GuzzleException
     */
    private function userLogin($data = [], string $sessionKey = ''): ?Json
    {
        $miniType = $data['mini_type'];
        $where = ['mini_type' => $miniType];
        switch ($miniType)
        {
            case 1:
                $third_id = $data['user_id'];
                $nickname = $data['nickname'] ?? '';
                $avatar = $data['avatar'] ?? '';
                $sex = $data['sex'];
                $unionid = '';
                $openid = $third_id;
                $city = $data['city'] ?? '';
                $province = $data['province'] ?? '';
                $country = $data['country'] ?? '';
                $where['openid'] = $data['user_id'];
                break;
            default:
                $third_id = $data['unionId'] ?? $data['openId'];
                $nickname = $data['nickName'] ?? '';
                $avatar = $data['avatarUrl'] ?? '';
                $sex = $data['gender'];
                $unionid = $third_id;
                $openid = $data['openId'];
                $city = $data['city'] ?? '';
                $province = $data['province'] ?? '';
                $country = $data['country'] ?? '';
                $where['unionid'] = $third_id;
        }

        try
        {
            $MiniAppUser = new ZlcrAstrMiniInfo();
            //验证是否注册过
            $mini_user = $MiniAppUser->where($where)->order('id desc')->find();
            if (!$mini_user || $mini_user['status'] == 1)
            {//如果用户没有注册 或者 注册用户已经注销了 ==> 注册账户
                //生成access_token和过期时间
                $tokenData = $this->getUserTokenInfo($third_id);

                $data = [
                    'tel' => '',
                    'password' => '',
                    'salt' => '',
                    'token' => $tokenData['token'],
                    'token_expire' => $tokenData['token_expire'],
                    'status' => 1,
                    'check_status' => 10, // 认证状态 10=>占星师考核  11=>待审核  12=>考核未通过 20=>资料填写 21=>资料待审核 22=>资料审核未通过 30=>培训   40=>认证完成（可接单）
                    'create_time' => $this->request->time(),
                    'regiest_env' => $miniType === 1 ? 'ALIPAY' : 'WEAPP'
                ];

                $astrId = (new ZlcrAstr())->insertGetId($data);

                $avatarInfo = null;
                if ($avatar)
                {
                    $avatarInfo = Util::wxImgUploadOss($avatar, 'uploads/avatar');
                    if (false === $avatarInfo['success'])
                    {
                        Log::write('占星师后台小程序Login Controller  上传三方头像失败，' . $avatarInfo['message'], 'error');
                    }
                }
                $avatar = $avatarInfo['success'] ? $avatarInfo['url'] : $avatar;

                $mini_user = [
                    'mini_type' => $miniType,
                    'nickname' => $nickname ?? '',
                    'avatar' => $avatar,
                    'sex' => $sex,
                    'unionid' => $unionid,
                    'openid' => $openid,
                    'city' => $city,
                    'province' => $province,
                    'country' => $country,
                    'create_time' => time(),
                    'status' => 0,
                    'astr_id' => $astrId,
                ];
                $MiniAppUser->SavaData($mini_user);

                $astrInfo = [
                    'check_status' => $data['check_status'],
                    'id' => $astrId,
                    'tel' => '',
                    'status' => $data['status'],
                    'token' => $data['token'],
                    'token_expire' => $data['token_expire'],
                ];
            } elseif ($mini_user && $mini_user['status'] == 2)
            {   // 用户已被禁止登陆
                throw new ApiException('该账号禁止登录');
            } else
            {
                $astrId = $mini_user['astr_id'];
                $astrModel = new ZlcrAstr();
                $astrInfo = $astrModel->getInfoById($astrId);
                $simulation_astrologer = config('simulation_astrologer');

                if ((isset($simulation_astrologer['is_support_muti']) && 1 === (int)$simulation_astrologer['is_support_muti']) && $astrInfo['token_expire'] > $this->request->time())
                { //  支持多平台 并且access_token还未过期 续费
                    $astrTokenData['token_expire'] = strtotime('+' . config('simulation_astrologer.login_time_out_seconds') . ' seconds');
                } else
                {   // 登录
                    $astrTokenData = $this->getUserTokenInfo($third_id);
                    $astrInfo['token'] = $astrTokenData['token'];
                }


                $astrModel->UpdateData($astrInfo['id'], ['id' => $astrInfo['id']], $astrTokenData);
            }

            // if ($sessionKey)
            // {
            //     $CacheSessionKey = $miniType . '_SESSION_KEY';
            //     Cache::set(constant('self::' . $CacheSessionKey) . ':' . $data['mid'], $sessionKey, 300);
            // }

            $roles = ['astrologer_not_completed'];//未完成章程学习的角色
            $path = '/dashboard/index';//跳转的路径，默认跳转首页
            if ($astrInfo['check_status'] == 40)
            {
                $roles = ['astrologer'];//完成章程学习的角色/特邀用户
            } else
            {
                $path = '/account/index';//跳转到考试页面
            }

            $data = [
                'token' => (new Aes())->encryptWithOpenssl($astrInfo['token'] . '||' . $astrInfo['id'], 'astradmin'),
                'roles' => $roles,
                'path' => $path,
                'has_bind_tel' => $astrInfo['tel'] ? 1 : 0,
                'step' => User::$statusInfo[$astrInfo['check_status']]
            ];
            //状态 1=>正常 0=>禁止（禁止登陆） 2=>账号冻结（可以登录，但是不能接单
            if ((int)$astrInfo['status'] === 2)
            {
                Log::write("占星师后台小程序登录成功；占星师ID【{$astrInfo['id']}】：您的账号已被冻结，请联系客服反馈问题～", 'error');
                return show(406, '您的账号已被冻结，请联系客服反馈问题～', $data);//403: 在其他客户端登录; 405 未登录，前往登录；406：前端提示错误信息，但是返回的数据正常
            } elseif ((int)$astrInfo['status'] === 0)
            {
                Log::write('占星师【' . $astrInfo['id'] . '】小程序登录失败：该用户禁止登陆，请联系客服反馈问题～', 'error');
                return show(config('code.error'), '该用户禁止登陆，请联系客服反馈问题～');
            } else
            {
                return show(config('code.success'), '登录成功', $data);
            }
        }
        catch (Exception $e)
        {
            Log::write('占星师后台小程序用户注册/登录失败，' . $e->getMessage() . '失败行数：' . $e->getLine(), 'error');
            throw new ApiException('登录失败', 200, 0);
        }
    }

    /**
     * 注册
     */
    public function regiest()
    {
        $env_type = $this->request->header('app-type', 'PC'); // app-type = ALIPAY(支付宝小程序) ｜ WEAPP(微信小程序) ｜ WEB（H5） ｜ PC（PC后台）；default = PC
        $tel = trim(input('tel/s', ''));
        $password = trim(input('password/s', ''));
        $code = trim(input('code/d', 0));

        // 手机号是否存在
        if (!preg_match("/^1[3456789]{1}\d{9}$/", $tel))
        {
            Log::write('手机号错误：' . $tel, 'error', true);
            throw new ApiException('手机号错误', 200, 0);
        }

        if (!$code)
        {
            return show(config('code.error'), '验证码错误');
        }

        $appConfig = config('app');
        $areaCode = 86;
        // 短信登录时，检测验证码是否正确
        if (!empty($code))
        {
            if (!in_array($tel, $appConfig['mobile'])
                || ($areaCode === 86 && $code != substr($appConfig['code'] * (substr($tel, 3, 6)), 0, 6)))
            {
                //苹果和安卓应用商店审核账号，固定验证码为666666
                if (($tel == '13045794520' && $code == '666666')
                    || ($tel == '13261877781' && $code == '666666'))
                {

                } else
                {
                    //  validate 严格校验
                    $mobileCode = Alidayu::getInstance()->checkSmsIdentify($tel, $areaCode);

                    if ($code != $mobileCode)
                    {
                        return show(config('code.error'), '短信验证码错误');
                    }
                }
            }
        }

        if (!$password)
        {
            return show(config('code.error'), '请输入密码');
        }

        if (!preg_match("/^(?![^a-zA-Z]+$)(?!\\\\D+$).{8,16}$/", $password))
        {
            return show(config('code.error'), '密码格式错误（8~16个字符，包含字母和数字）');
        }

        try
        {
            $res = (new ZlcrAstr())->where('tel', $tel)->field('id,token')->find();
            if ($res)
            {
                if (Cache::get(json_encode("['tel' => $tel, 'password' => $password, 'code' => $code]", JSON_UNESCAPED_UNICODE)))
                {   //
                    return show(config('code.success'), '注册成功', [
                        'token' => (new Aes())->encryptWithOpenssl($res['token'] . '||' . $res['id'], 'astradmin')
                    ]);
                }
                return show(config('code.error'), '该手机号已经注册，请勿重复注册');
            }

            // 生成token  和 token到期时间
            $token = IAuth::setAppLoginToken($tel);

            $passwordData = self::passwordEncode($password);

            $data = [
                'tel' => $tel,
                'password' => $passwordData['password'],
                'salt' => $passwordData['salt'],
                'token' => $token,
                'token_expire' => strtotime('+' . config('app.login_time_out_day') . ' days'),
                'status' => 1,
                'check_status' => 10, // 认证状态 10=>占星师考核  11=>待审核  12=>考核未通过 20=>资料填写 21=>资料待审核 22=>资料审核未通过 30=>培训   40=>认证完成（可接单）
                'create_time' => $this->request->time(),
                'regiest_env' => $env_type
            ];

            $uid = (new ZlcrAstr())->insertGetId($data);
        }
        catch (\Exception $e)
        {
            Log::write('占星师账号注册失败：' . $e->getMessage(), 'error', true);
            return show(config('code.error'), '注册失败,请稍后重试');
        }

        // 验证码过期
        Cache::rm($tel);

        Cache::set(json_encode("['tel' => $tel, 'password' => $password, 'code' => $code]", JSON_UNESCAPED_UNICODE), 1, 2);

        return show(config('code.success'), '注册成功', [
            'token' => (new Aes())->encryptWithOpenssl($token . '||' . $uid, 'astradmin'),
            'roles' => ['astrologer_not_completed']//未完成章程学习的角色
        ]);
    }

    /**
     * 重设密码
     */
    public function passwordReset()
    {
        $tel = trim(input('tel/s', ''));
        $password = trim(input('password/s', ''));
        $code = trim(input('code/d', 0));

        // 手机号是否存在
        if (!preg_match("/^1[3456789]{1}\d{9}$/", $tel))
        {
            Log::write('手机号错误：' . $tel, 'error', true);
            throw new ApiException('手机号错误', 200, 0);
        }

        $appConfig = config('app');
        $areaCode = 86;
        // 短信登录时，检测验证码是否正确
        if (!empty($code))
        {
            if (!in_array($tel, $appConfig['mobile'])
                || ($areaCode === 86 && $code != substr($appConfig['code'] * (substr($tel, 3, 6)), 0, 6)))
            {
                //苹果和安卓应用商店审核账号，固定验证码为666666
                if (($tel == '13045794520' && $code == '666666')
                    || ($tel == '13261877781' && $code == '666666'))
                {

                } else
                {
                    //  validate 严格校验
                    $mobileCode = Alidayu::getInstance()->checkSmsIdentify($tel, $areaCode);

                    if ($code != $mobileCode)
                    {
                        return show(config('code.error'), '短信验证码错误');
                    }
                }
            }
        } else
        {
            return show(config('code.error'), '请输入验证码');
        }

        if (!$password)
        {
            return show(config('code.error'), '请输入密码');
        }

        if (!preg_match("/^(?![^a-zA-Z]+$)(?!\\\\D+$).{8,16}$/", $password))
        {
            return show(config('code.error'), '密码格式错误（8~16个字符，包含字母和数字）');
        }

        try
        {
            $res = (new ZlcrAstr())->where('tel', $tel)->field('id,token')->find();
            if ($res)
            {//查询到了用户，那么进行修改密码操作
                $passwordData = self::passwordEncode($password);

                (new ZlcrAstr())->UpdateData($res['id'], ['id' => $res['id']], [
                    'password' => $passwordData['password'],
                    'salt' => $passwordData['salt'],
                    'token' => '',
                    'modify_password' => 1,//修改密码标识 0=> 未修改过，1=>已修改
                    'update_time' => time(),
                    'password_reset_at' => time()
                ]);
                return show(config('code.success'), '修改成功，请重新登录！');
            } else
            {//没有查到用户，提示用户
                return show(config('code.error'), '修改失败，该手机号未注册！');
            }

        }
        catch (\Exception $e)
        {
            Log::write('占星师账号修改密码失败：' . $e->getMessage(), 'error', true);
            return show(config('code.error'), '修改密码,请稍后重试');
        }

        // 验证码过期
        Cache::rm($tel);

        Cache::set(json_encode("['tel' => $tel, 'password' => $password, 'code' => $code]", JSON_UNESCAPED_UNICODE), 1, 2);

        return show(config('code.success'), '密码修改成功');
    }

    /**
     * 密码生成
     * @param string $password
     * @return array
     */
    public static function passwordEncode(string $password): array
    {
        $salt = mb_strcut(uniqid(), 0, 10);

        $newPassword = md5(md5($password . $salt) . $salt);

        return [
            'salt' => $salt,
            'password' => $newPassword
        ];
    }

    /**
     * 获取session key
     * @param $code
     * @param string $env_type SWAN：百度小程序；WEAPP：微信小程序；QQ:qq小程序；TT：字节跳动小程序；
     * @param array $mini_item_conf 单个小程序的配置
     * @return string
     */
    private function getSessionKey($code, $env_type = '', $mini_item_conf = []): string
    {
        try
        {
            $access_token_url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $mini_item_conf['app_id'] . '&secret=' . $mini_item_conf['app_secret'] . '&js_code=' . $code . '&grant_type=authorization_code';

            if (!empty($access_token_url))
            {
                $result = file_get_contents($access_token_url);
                $result_arr = json_decode($result, true);
                if (isset($result_arr['session_key']))
                {
                    return $result_arr['session_key'];
                }
            }
        }
        catch (Exception $e)
        {
            Log::write('获取 ' . $env_type . ' session key异常，信息为：' . $e->getMessage() . PHP_EOL, 'error', true);
        }

        return '';
    }

    /**
     * 微信或者QQ，检验数据的真实性，并且获取解密后的.
     * @param $appId
     * @param $sessionKey
     * @param $encryptedData string 加密的用户数据
     * @param $iv string 与用户数据一同返回的初始向量
     * @return array | int 成功返回详细信息，失败返回false，例如：avatarUrl = "https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epF3yvjxqJuRB9KydwTVHqEpia3wxwnBOkFUsDhMV9GwO9vcJ6tHlRbsEuicC9p7e859QCMVcRAt1uA/132"
     * city = "Chaoyang"
     * country = "China"
     * gender = {int} 1
     * language = "zh_CN"
     * nickName = "李柏楠"
     * openId = "oVpmX5KHiQXXabinAnBbYy3Um2ns"
     * province = "Beijing"
     * unionId = "oIVMdt71AOrtGBFSlYBe8zSDl5_o"
     * watermark = {array} [2]
     * appid = "wxa417c0a7a1e75643"
     * timestamp = {int} 1575437969
     */
    protected function wechatOrQQDecrypt($appId, $sessionKey, $encryptedData, $iv)
    {
        //qq登录没有unionid，有可能是我的测试号的原因，切换后再测试【解决：使用星座女神的qq号，登录qq开发平台进行的关联】
        if (strlen($sessionKey) !== 24)
        {
            return false;
        }
        $aesKey = base64_decode($sessionKey);
        if (strlen($iv) !== 24)
        {
            return false;
        }
        $aesIV = base64_decode($iv);
        $aesCipher = base64_decode($encryptedData);
        $result = openssl_decrypt($aesCipher, 'AES-128-CBC', $aesKey, 1, $aesIV);
        $data_arr = json_decode($result, true);
        if ($data_arr === NULL)
        {
            return false;
        }
        if ($data_arr['watermark']['appid'] !== $appId)
        {
            return false;
        }
        return $data_arr;
    }

    /**
     * 获取用户token信息
     * @param string $third_id
     * @return array
     */
    public function getUserTokenInfo($third_id = ''): array
    {
        // 生成token  和 token到期时间
        $token = IAuth::setAppLoginToken($third_id);
        $data = [
            'token' => $token,
            'token_expire' => strtotime('+' . config('simulation_astrologer.login_time_out_seconds') . ' seconds')
        ];
        return $data;
    }
}
